/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.io
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public class SWFInput extends IOBase
	{

		private static const NULL_CHAR:String = String.fromCharCode(0);

		private var _tmpBytes:ByteArray;

		public function SWFInput(bytes:ByteArray)
		{
			super(bytes);

			_tmpBytes = new ByteArray();
		}

		public function readBytes(length:uint):ByteArray
		{
			_bitCursor = 0;
			var bytes:ByteArray = new ByteArray();

			if (length > 0)
			{
				_bytes.readBytes(bytes, 0, length);
			}

			return bytes;
		}

		public function readDOUBLE():Number
		{
			_bitCursor = 0;
			return _bytes.readDouble();
		}

		public function readEncodedS32():int
		{
			return readEncodedU32();
		}

		public function readEncodedU32():uint
		{
			_bitCursor = 0;

			var result:int = _bytes.readUnsignedByte();
			if (!(result & 0x00000080))
			{
				return result;
			}

			var b:int = _bytes.readUnsignedByte() << 7;
			result = result & 0x0000007f | b;
			if (!(result & 0x00004000))
			{
				return result;
			}

			b = _bytes.readUnsignedByte() << 14;
			result = result & 0x00003fff | b;
			if (!(result & 0x00200000))
			{
				return result;
			}

			b = _bytes.readUnsignedByte() << 21;
			result = result & 0x001fffff | b;
			if (!(result & 0x10000000))
			{
				return result;
			}

			b = _bytes.readUnsignedByte() << 28;
			return result & 0x0fffffff | b;
		}

		public function readFB(length:uint):Number
		{
			return readSB(length) / 65536;
		}

		public function readFIXED():Number
		{
			_bitCursor = 0;
			return _bytes.readInt() / 65536;
		}

		public function readFIXED8():Number
		{
			_bitCursor = 0;
			return _bytes.readShort() / 256;
		}

		public function readFLAG():Boolean
		{
			var buffer:uint = _bitBuffer;
			var cursor:uint = _bitCursor;
			if (cursor === 0)
			{
				buffer = _bytes.readUnsignedByte();
				cursor = 8;
			}

			var result:uint = buffer >> (cursor - 1);
			--cursor;
			_bitBuffer = buffer & (0xff >> (8 - cursor));
			_bitCursor = cursor;

			return result !== 0;
		}

		public function readFLOAT16():Number
		{
			var val:uint = readUI16();
			var sign:int = val & 0x8000 ? -1 : 1;
			var exp:int = (val >> 10) & 0x1f;
			var fraction:Number = val & 0x3ff;
			if (exp === 0)
			{
				if (fraction === 0)
				{
					return 0;
				}
				else
				{
					return fraction;
				}
			}
			if (exp === 0x1f)
			{
				if (fraction === 0)
				{
					return Number.POSITIVE_INFINITY;
				}
				else
				{
					return Number.NaN;
				}
			}

			return sign * Math.pow(2, exp - 16) * (1 + fraction / 0x400);
		}

		public function readFLOAT32():Number
		{
			return _bytes.readFloat();
		}

		public function readSB(length:uint):int
		{
			if (length === 0)
			{
				return 0;
			}

			var cursor:uint = _bitCursor;
			var buffer:uint = _bitBuffer;
			var len:uint = length;

			if (cursor === 0)
			{
				buffer = _bytes.readUnsignedByte();
				cursor = 8;
			}

			var result:uint = 0, s:int;
			while (true)
			{
				s = len - cursor;
				if (s > 0)
				{
					result |= buffer << s;
					len -= cursor;
					buffer = _bytes.readUnsignedByte();
					cursor = 8;
				}
				else
				{
					result |= buffer >> -s;
					cursor -= len;
					buffer &= 0xff >> (8 - cursor)
					break;
				}
			}

			_bitBuffer = buffer;
			_bitCursor = cursor;
			s = 32 - length;
			return (result << s) >> s;
		}

		public function readSI16():int
		{
			_bitCursor = 0;
			return _bytes.readShort();
		}

		public function readSI24():int
		{
			_bitCursor = 0;
			var result:int = _bytes.readUnsignedByte();
			result |= _bytes.readUnsignedByte() << 8;
			result |= _bytes.readByte() << 16;
			return result;
		}

		public function readSI32():int
		{
			_bitCursor = 0;
			return _bytes.readInt();
		}

		public function readSI64():Number
		{
			_bitCursor = 0;
			var val1:Number = _bytes.readUnsignedInt();
			var val2:Number = _bytes.readInt();
			return val2 * 4294967296 + val1;
		}

		public function readSI8():int
		{
			_bitCursor = 0;
			return _bytes.readByte();
		}

		public function readString(length:uint, charset:String = null):String
		{
			_bitCursor = 0;
			if (length > 0)
			{
				_tmpBytes.clear();
				if (charset !== null)
				{
					_bytes.readBytes(_tmpBytes, 0, length);
					if (_tmpBytes[_tmpBytes.length - 1] === 0)
					{
						return _tmpBytes.readMultiByte(length, charset) + NULL_CHAR;
					}
					return _tmpBytes.readMultiByte(length, charset);
				}

				_bytes.readBytes(_tmpBytes, 0, length);
				if (_tmpBytes[_tmpBytes.length - 1] === 0)
				{
					return _tmpBytes.readUTFBytes(length) + NULL_CHAR;
				}
				return _tmpBytes.readUTFBytes(length);
			}

			var bytes:ByteArray = new ByteArray();
			var byte:int;
			while ((byte = _bytes.readByte()) !== 0)
			{
				bytes.writeByte(byte);
			}
			bytes.position = 0;

			if (charset !== null)
			{
				return bytes.readMultiByte(bytes.length, charset);
			}

			return bytes.readUTFBytes(bytes.length);
		}

		public function readUB(length:uint):uint
		{
			if (length === 0)
			{
				return 0;
			}

			var cursor:uint = _bitCursor;
			var buffer:uint = _bitBuffer;
			var len:uint = length;

			if (cursor === 0)
			{
				buffer = _bytes.readUnsignedByte();
				cursor = 8;
			}

			var result:uint = 0, s:int = 0;
			while (true)
			{
				s = len - cursor;
				if (s > 0)
				{
					result |= buffer << s;
					len -= cursor;
					buffer = _bytes.readUnsignedByte();
					cursor = 8;
				}
				else
				{
					result |= buffer >> -s;
					cursor -= len;
					buffer &= 0xff >> (8 - cursor)
					break;
				}
			}

			_bitBuffer = buffer;
			_bitCursor = cursor;
			return result;
		}

		public function readUI16():uint
		{
			_bitCursor = 0;
			return _bytes.readUnsignedShort();
		}

		public function readUI32():uint
		{
			_bitCursor = 0;
			return _bytes.readUnsignedInt();
		}

		public function readUI8():uint
		{
			_bitCursor = 0;
			return _bytes.readUnsignedByte();
		}
	}
}
